package org.swxjava.assembler;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.swxjava.util.BeanUtils;
import org.swxjava.util.Log;
import org.swxjava.util.LogFactory;

public class DefaultConverterManager implements ConverterManager {

	private static final Log LOG = LogFactory
			.getLog(DefaultConverterManager.class);

	/** The list of the converters */
	private Map<String, Class> converterTypes = new HashMap<String, Class>();

	/** The list of the configured converters */
	private Map<String, Converter> converters = new HashMap<String, Converter>();

	public void setConverters(Map<String, Converter> converters) {
		this.converters = converters;
	}
	
	public ObjectCompiler convert(Object object) throws MarshallException {
		Converter converter = getConverter(object);
		if (converter == null) {
			LOG.error("There are no converter for " + object);
			converter = getConverter(Void.TYPE);
		}
		return converter.convert(object);
	}

	public void addConverterType(String id, String className) {
		Class converterType = BeanUtils.getClassForName(className);
		if (converterType != null) {
			if (BeanUtils.isImplements(converterType, Converter.class)) {
				if (LOG.isDebugEnabled()) {
					LOG.debug("- adding converter: " + id + " = " + className);
				}
				converterTypes.put(id, converterType);
			} else {
				LOG.warn("The converter class [" + className + "] does not implement Converter. id=" + id);
			}
		}
	}

	public void addConverter(String match, String type, Map params)	throws IllegalArgumentException, InstantiationException, IllegalAccessException {
		Class converterType = converterTypes.get(type);
		if (converterType == null) {
			String errorMsg = "Could not find the converter named : " + type;
			LOG.error(errorMsg);
			throw new IllegalArgumentException(errorMsg);
		}
		Converter converter = (Converter) converterType.newInstance();
		if (params != null) {
			for (Iterator<Map.Entry> it = params.entrySet().iterator(); it.hasNext();) {
				Map.Entry entry = it.next();
				String name = (String) entry.getKey();
				String value = (String) entry.getValue();
				try {
					BeanUtils.setProperty(converter, name, value);
				} catch (IllegalArgumentException e) {
					LOG.warn("- Converter[" + type + "] does not allow parameter named : " + name);
				}
			}
		}
		converter.setConverterManager(this);
		addConverter(match, converter);
	}

	public void addConverter(String match, Converter converter)
			throws IllegalArgumentException {
		Converter other = (Converter) converters.get(match);
		if (other != null) {
			LOG.warn("Conflicted converters for " + match + ". Using " + converter.getClass().getName() + " in place of " + other.getClass().getName());
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("- adding converter mapping : " + converter.getClass().getSimpleName() + " for " + match);
		}
		converters.put(match, converter);
	}

	private Converter getConverter(Object object) {
		if (object == null) {
			return getConverter(Void.TYPE);
		}
		return getConverter(object.getClass());
	}

	private Converter getConverter(Class paramType) {
		Converter converter = getConverterAssignableFrom(paramType);
		if (converter != null) {
			return converter;
		}

		String lookup = paramType.getName();

		// Before we start trying for a match on package parts we check for
		// dynamic proxies
		if (lookup.startsWith("$Proxy")) {
			converter = (Converter) converters.get("$Proxy*");
			if (converter != null) {
				return converter;
			}
		}

		while (true) {
			// Can we find a converter using wildcards?
			converter = (Converter) converters.get(lookup + ".*");
			if (converter != null) {
				return converter;
			}

			// Arrays can have wildcards like [L* so we don't require a '.'
			converter = (Converter) converters.get(lookup + '*');
			if (converter != null) {
				return converter;
			}

			// Give up if the name is now empty
			if (lookup.length() == 0) {
				break;
			}

			// Strip of the component after the last .
			int lastdot = lookup.lastIndexOf('.');
			if (lastdot != -1) {
				lookup = lookup.substring(0, lastdot);
			} else {
				int arrayMarkers = 0;
				while (lookup.charAt(arrayMarkers) == '[') {
					arrayMarkers++;
				}

				if (arrayMarkers == 0) {
					// so we are out of dots and out of array markers
					// bail out.
					break;
				}

				// We want to keep the type marker too
				lookup = lookup.substring(arrayMarkers - 1, arrayMarkers + 1);

				// Now can we find it?
				converter = (Converter) converters.get(lookup);
				if (converter != null) {
					return converter;
				}
			}
		}

		return null;
	}

	private Converter getConverterAssignableFrom(Class paramType) {
		if (paramType == null) {
			return null;
		}
		Converter converter = (Converter) converters.get(paramType.getName());
		if (converter != null) {
			return converter;
		}

		// Try to find from the interfaces
		Class[] interfaces = paramType.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			converter = getConverterAssignableFrom(interfaces[i]);
			if (converter != null) {
				converters.put(paramType.getName(), converter);
				return converter;
			}
		}

		// Try to find from the super class
		converter = getConverterAssignableFrom(paramType.getSuperclass());
		if (converter != null) {
			converters.put(paramType.getName(), converter);
		}
		return converter;
	}

}
